/*
 * Vectrics 
 * Created on Jan 30, 2004
 *
 */
package org.vectrics.imaging.client;

import org.apache.log4j.*;
import java.io.*;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;

import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.crypto.engines.BlowfishEngine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.vectrics.VectricsException;


/**
 * @author mmoore
 *
 * To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Generation - Code and Comments
 */
public class EncryptedFileUtils {
	private transient static Logger log = Logger.getLogger(EncryptedFileUtils.class);
	private static int PAD_COUNT_SIZE = 8;
	public static int MAX_FOLDERS_PER_BIN = 100;
	
	/**
	 * 
	 */
	public EncryptedFileUtils() {
		super();
		// TODO Auto-generated constructor stub
	}

	public static void encryptFile(String from, String to) {
		try {
			FileInputStream fileStream = new FileInputStream(from);
			int count = fileStream.available();
			byte inBytes[] = new byte[count];
			fileStream.read(inBytes);
			fileStream.close();
			saveEncrypted(to, inBytes);
		}
		catch (FileNotFoundException fe) {
			log.error("FileNotFoundException in encryptFile", fe);
		}
		catch (IOException ie) {
			log.error("IOException in encryptFile", ie);
		}
		catch (VectricsException ie) {
			log.error("IOException in encryptFile", ie);
		}
	} 
	
	
	public static String getNextSeriesName(String targetDir) {
		log.debug("finding next series name");
		int maxBinNumber = 0;
		DirectoryManager targetDirMgr = new DirectoryManager();
		targetDirMgr.clearFileExtensions();
		targetDirMgr.addFileExtension("*");
		targetDirMgr.setDirectory(targetDir);
		Vector files = targetDirMgr.findFileSet();
				
		Iterator iter = files.iterator();
		while (iter.hasNext()) {
			String file = (String)iter.next();
			if ((file.startsWith("s") && (file.endsWith("eji")))) {
				log.debug("found file with 's'");
				int posDash = file.indexOf("-");
				if (posDash > 0) {
					String temp = file.substring(posDash + 1);
					int posDash2 = temp.indexOf("-");
					if (posDash2 > 0) {
						log.debug("finding file bin number - fileName after 'bin' " + temp);
						String binNumber = temp.substring(0, posDash2);
						log.debug("finding file bin number - binNumber = " + binNumber);
						int binNum = Integer.parseInt(binNumber);
						if (binNum >= maxBinNumber) {
							maxBinNumber = binNum + 1;
						}
					}
				}
			}
		}
		
		String result = "s-" + maxBinNumber + "-";
		return(result);
	}


	public static String getBinFolderName(long binNumber) {
		String folderName = "bin-" + binNumber;
		return(folderName);
	}

	public static int getNumberOfFilesInDirectory(String directory) {
		int fileCount = 0;
		DirectoryManager dirMgr = new DirectoryManager();
		dirMgr.setDirectory(directory);
		dirMgr.verifyDirectory();
		dirMgr.addFileExtension("*");
		fileCount = dirMgr.findFileSet().size();
		return(fileCount);
	}


	public static String getNextBinName(String targetDir) {
		long currentBinNumber = getCurrentBinNumber(targetDir);
		String binFolderName = getBinFolderName(currentBinNumber);
		int numberOfFilesInBin = getNumberOfFilesInDirectory(targetDir + "/" + binFolderName);
		log.info("Current bin = " + binFolderName);
		log.info("Number of files in current bin = " + numberOfFilesInBin);
		if (numberOfFilesInBin > MAX_FOLDERS_PER_BIN) {
			binFolderName = getBinFolderName(currentBinNumber + 1);
		}
		return(binFolderName);
	}
	
	public static long getCurrentBinNumber(String targetDir) {
		log.debug("finding next series name");
		long maxBinNumber = -1;
		DirectoryManager targetDirMgr = new DirectoryManager();
		targetDirMgr.setSubDirectoriesOnly(true);
		targetDirMgr.clearFileExtensions();
		targetDirMgr.setDirectory(targetDir);
		Vector files = targetDirMgr.findFileSet();
				
		Iterator iter = files.iterator();
		while (iter.hasNext()) {
			String file = (String)iter.next();
			if (file.startsWith("bin")) {
				log.debug("found file with 'bin'");
				int posDash = file.indexOf("-");
				if (posDash > 0) {
					String temp = file.substring(posDash + 1);
					int binNum = Integer.parseInt(temp);
					if (binNum >= maxBinNumber) {
						maxBinNumber = binNum;
					}
				}
			}
		}
		
		if (maxBinNumber <= 0) {
			Date now = new Date();
			maxBinNumber = now.getTime();
		}
		return(maxBinNumber);
	}

	
	public static void encryptDirectoryImages(String directoryName, String targetDir, boolean recurse) {
		//parentPanel.setMinimumSize(new Dimension(100, 10));
		log.debug("encryptDirectoryImages() - ecrypting files in dir = " + directoryName);
		
		DirectoryManager dirMgr = new DirectoryManager();
		dirMgr.clearFileExtensions();
		dirMgr.addFileExtension(".jpg");
		dirMgr.setDirectory(directoryName);
		
		
		Vector files = dirMgr.findFileSet();
		Iterator iter = files.iterator();
		int fileNumber = 0;
		String binName = getNextBinName(targetDir);
		String seriesName = getNextSeriesName(targetDir + "/" + binName);
		log.info("encryptDirectoryImages() - bin name = " + binName);
		log.info("encryptDirectoryImages() - series name = " + seriesName);
		while (iter.hasNext()) {
			fileNumber++;
			String file = (String)iter.next();
			String encFileName = targetDir + "/" + binName + "/" + seriesName + fileNumber + ".eji";
			log.debug("Encrypted file = " + encFileName); 
			encryptFile(directoryName + "/" + file, encFileName);			
		}
		
		if (recurse) {
			log.debug("encryptDirectoryImages() - looking at subdirectories");
			DirectoryManager subDirManager = new DirectoryManager();
			subDirManager.setDirectory(directoryName);
			subDirManager.setSubDirectoriesOnly(true);
			Iterator subIter = subDirManager.findFileSet().iterator();
			if (subIter.hasNext()) {
				while (subIter.hasNext()) {
					String file = (String)subIter.next();
					String subDir = directoryName + "/" + file;
					log.info("Encrypting file from sub directory: " + subDir);
					encryptDirectoryImages(subDir, targetDir, recurse);
				}
			}
			else {
				log.info("No subdirectories found in dir = " );
			}
			
		}
		else {
			log.info("encryptDirectoryImages() - recurse is false, not looking at subdirs");
		}
		
		
	}	
	
	public static byte[] readEncryptedFile(String fileName) {
		byte imageBytes[] = null;
		byte inBytes[] = null;
		byte outBytes[] = null;
		try {
			FileInputStream fileStream = new FileInputStream(fileName);
			int count = fileStream.available();
			inBytes = new byte[count];
			outBytes = new byte[count];
			fileStream.read(inBytes);
			fileStream.close();
		}
		catch (FileNotFoundException fe) {
			log.error("readEncryptedFile() - file not found exceptiong for file: " + fileName, fe);
		}
		catch (IOException ie) {
			log.error("IOException reading data from encrypted file: " + fileName);
		}
		
		BlowfishEngine engine = new BlowfishEngine();
		BufferedBlockCipher cipher = new BufferedBlockCipher(engine);
		KeyParameter param = new KeyParameter(Hex.decode("3000000000000000"));
		cipher.init(false, param);
		int len1 = cipher.processBytes(inBytes, 0, inBytes.length, outBytes, 0);

		try
		{
			cipher.doFinal(outBytes, len1);
		}
		catch (CryptoException e)
		{
			log.error("CrytoException saving encrypted file");
		}
		
		log.debug("find number of padding bytes at end");
		int padCount = 0;
		String padCountStr = "";
		for (int i = 0; i < PAD_COUNT_SIZE; i++) {
			//int digitByte = outBytes[i];
			//log.debug("digitByte = " + digitByte);
			//padCount += (padCount * 10) + digitByte;
			padCountStr += "" + (int)outBytes[i];
			log.debug("padCountStr = " + padCountStr);
		}
		padCount = Integer.parseInt(padCountStr);
		log.debug("Reading encrypted file - Pad Count = " + padCount);
		
		int dataLength = outBytes.length - (PAD_COUNT_SIZE + padCount);
		
		log.debug("Creating array for bytes[] - the raw data held in this class for the image.  Size = " + dataLength);
		imageBytes = new byte[dataLength];
		for (int i = 0; i < dataLength; i++) {
			imageBytes[i] = outBytes[i + PAD_COUNT_SIZE];
		}
		
		log.debug("After encryption - imageBytes.size = " + imageBytes.length);
		//logBytes("Decrypted bytes (image bytes)", imageBytes);

/*
		try {

			Date now = new Date();		
			FileOutputStream outFile = new FileOutputStream("test-file" + now.getTime() + ".jpg");
			outFile.write(bytes);
		}
		catch (Exception ex) {
			log.error("Exception writing test output file");
		}
*/		
	return(imageBytes);
	}
	
	public static void saveEncrypted(String fileName, byte[] imageBytes) throws VectricsException  {
		log.debug("saveEncrypted()");
		int count = imageBytes.length;
		BlowfishEngine engine = new BlowfishEngine();
		int blockSize = engine.getBlockSize();
		log.info("Block size = " + blockSize);
		
		log.debug("We are adding PAD_COUNT_SIZE digits to start of encrypted file, and padding at end to make blocks align");
		int outDataLength = imageBytes.length + PAD_COUNT_SIZE;
		int numberOfBlocks = 1 + (outDataLength / blockSize);
		int numberOfPadBytes = (numberOfBlocks * blockSize) - outDataLength;
		
		byte inBytes[] = new byte[(numberOfBlocks * blockSize)];
		String numberOfPadBytesStr = "" + numberOfPadBytes;
		while (numberOfPadBytesStr.length() < PAD_COUNT_SIZE) {
			numberOfPadBytesStr = "0" + numberOfPadBytesStr;
		}
		
		log.debug("fill inBytes with padding count and input data - paddingCountStr = " + numberOfPadBytesStr);
		int inputPos = 0;
		for (int i = 0; i < PAD_COUNT_SIZE; i++) {
			String digitStr = numberOfPadBytesStr.substring(i, i + 1);
			int digit = Integer.parseInt(digitStr);
			inBytes[inputPos] = (byte)digit;
			inputPos++;  
		}
		
		for (int i = 0; i < imageBytes.length; i++) {
			inBytes[inputPos] = imageBytes[i];
			inputPos++;
		}
		
		for (int i = 0; i < numberOfPadBytes; i++) {
			inBytes[inputPos] = (byte)0xdd;
			inputPos++;
		}
		
		log.debug("Finished building inBytes");
		
		byte outBytes[] = new byte[(numberOfBlocks * blockSize)];
		
		BufferedBlockCipher cipher = new BufferedBlockCipher(engine);
		KeyParameter param = new KeyParameter(Hex.decode("3000000000000000"));
		cipher.init(true, param);
		int len1 = cipher.processBytes(inBytes, 0, inBytes.length, outBytes, 0);

		try
		{
			cipher.doFinal(outBytes, len1);
		}
		catch (CryptoException e)
		{
			log.error("CrytoException saving encrypted file");
		}
		
		try {
			FileOutputStream outFile = new FileOutputStream(fileName);
			outFile.write(outBytes);
			log.info("Wrote encrypted file: " + fileName);
		}
		catch (FileNotFoundException fe) {
			log.error("Could not create file: " + fileName, fe);
		}
		catch (IOException ie) {
			log.error("IOException writing file: " + fileName, ie);
		}
		
		//logBytes("Before encryption bytes", bytes);
		//logBytes("Encrypted bytes with padding", inBytes);
	}


}
